/*
 * This file is part of the cSploit.
 *
 * Copyleft of Massimo Dragano aka tux_mind <tux_mind@csploit.org>
 *
 * cSploit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * cSploit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with cSploit.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.csploit.android.plugins;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import org.csploit.android.R;
import org.csploit.android.core.Plugin;
import org.csploit.android.core.System;
import org.csploit.android.gui.MsfPreferences;
import org.csploit.android.gui.dialogs.ChoiceDialog;
import org.csploit.android.gui.dialogs.ErrorDialog;
import org.csploit.android.gui.dialogs.FinishDialog;
import org.csploit.android.gui.dialogs.ListChoiceDialog;
import org.csploit.android.net.Target;
import org.csploit.android.net.Target.Exploit;
import org.csploit.android.net.datasource.Search;
import org.csploit.android.net.metasploit.MsfExploit;
import org.csploit.android.net.metasploit.Payload;
import org.csploit.android.net.metasploit.RPCClient;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import static org.csploit.android.net.metasploit.MsfExploit.Ranking;

public class ExploitFinder extends Plugin {

    private FloatingActionButton mSearchFloatingActionButton = null;
    private ProgressBar mSearchProgress = null;
    private ListView mListView = null;
    private Future job = null;
    private ExploitAdapter mAdapter = null;
    private boolean buttonPlayed = false;

    private static ExploitFinder UIThread = null;

    public class ExploitAdapter extends ArrayAdapter<Exploit> {

        class ExploitHolder {
            ImageView itemImage;
            TextView itemTitle;
            TextView itemDescription;
            TextView itemRanking;
        }

        public ExploitAdapter() {
            super(ExploitFinder.this, R.layout.plugin_exploit_finder_item, (List<Exploit>) System.getCurrentExploits());
        }



    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      View row = convertView;
      ExploitHolder holder;

      if (row == null) {
        LayoutInflater inflater = (LayoutInflater) ExploitFinder.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        row = inflater.inflate(R.layout.plugin_exploit_finder_item, parent, false);

        holder = new ExploitHolder();

        holder.itemImage = (ImageView) row.findViewById(R.id.itemIcon);
        holder.itemTitle = (TextView) row.findViewById(R.id.itemTitle);
        holder.itemDescription = (TextView) row.findViewById(R.id.itemDescription);
        holder.itemRanking = (TextView) row.findViewById(R.id.itemRanking);

        row.setTag(holder);
      } else {
        holder = (ExploitHolder) row.getTag();
      }

      Exploit exploit = getItem(position);

      if (exploit instanceof MsfExploit) {
        holder.itemTitle.setText
                (
                        Html.fromHtml
                                (
                                        "<b>" + exploit.getName() + "</b>"
                                )
                );
        final Ranking rank = ((MsfExploit) exploit).getRank();

        holder.itemRanking.setText(
                String.format(getString(R.string.msf_exploit_ranking),
                        getString(rank.getStringID())));
        holder.itemRanking.setTextColor(rank.getColor());
      } else
        holder.itemTitle.setText(exploit.getName());

      holder.itemTitle.setTextColor(ContextCompat.getColor(getContext(),
              exploit.isEnabled() ? R.color.app_color : R.color.gray_text));
      holder.itemTitle.setTypeface(null, Typeface.NORMAL);
      holder.itemImage.setImageResource(exploit.getDrawableResourceId());
      holder.itemDescription.setText(exploit.getSummary());

      return row;
    }
  }

  private OnItemClickListener listener = new OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View v, final int position, long id) {
      Exploit exp = mAdapter.getItem(position);

      if (exp == null)
        return;

      if (exp instanceof MsfExploit) {
        final MsfExploit msfEx = (MsfExploit) exp;
        final ArrayList<Integer> availableChoices = new ArrayList<Integer>();

        if (System.getMsfRpc() != null && !System.getMsfRpc().isRemote())
          availableChoices.add(R.string.exploit_launch);
        if (msfEx.getOptions() != null)
          availableChoices.add(R.string.exploit_edit_options);
        if (msfEx.getCurrentPayload() != null)
          availableChoices.add(R.string.payload_edit_settings);
        if (msfEx.getPayloads() != null && msfEx.getPayloads().size() > 1)
          availableChoices.add(R.string.exploit_choose_payload);
        if (msfEx.getTargets() != null && msfEx.getTargets().size() > 1)
          availableChoices.add(R.string.exploit_choose_target);
        if (msfEx.getUrl() != null && !msfEx.getUrl().isEmpty())
          availableChoices.add(R.string.open_url);
        if (msfEx.getDescription() != null)
          availableChoices.add(R.string.show_full_description);
        new ListChoiceDialog(R.string.choose_an_option, availableChoices.toArray(new Integer[availableChoices.size()]), ExploitFinder.this, new ChoiceDialog.ChoiceDialogListener() {
          @Override
          public void onChoice(int choice) {
            Intent intent;
            switch (availableChoices.get(choice)) {
              case R.string.exploit_launch:
                launchExploit(msfEx);
                break;
              case R.string.exploit_edit_options:
                intent = new Intent(ExploitFinder.this, MsfPreferences.class);
                System.setCurrentExploit(msfEx);
                ExploitFinder.this.startActivity(intent);
                break;
              case R.string.payload_edit_settings:
                intent = new Intent(ExploitFinder.this, MsfPreferences.class);
                System.setCurrentPayload(msfEx.getCurrentPayload());
                ExploitFinder.this.startActivity(intent);
                break;
              case R.string.exploit_choose_payload:
                new ListChoiceDialog("Choose a payload", msfEx.getPayloads().toArray(new Payload[msfEx.getPayloads().size()]), ExploitFinder.this, new ChoiceDialog.ChoiceDialogListener() {
                  @Override
                  public void onChoice(int choice) {
                    msfEx.setPayload(choice);
                  }
                }).show();
                break;
              case R.string.exploit_choose_target:
                new ListChoiceDialog("Choose a target", msfEx.getTargets().toArray(new String[msfEx.getTargets().size()]), ExploitFinder.this, new ChoiceDialog.ChoiceDialogListener() {
                  @Override
                  public void onChoice(int choice) {
                    msfEx.setTarget(choice);
                  }
                }).show();
                break;
              case R.string.show_full_description:
                new ErrorDialog(msfEx.getName(), msfEx.getDescription(), ExploitFinder.this).show();
                break;
              case R.string.open_url:
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(msfEx.getUrl())));
                break;
            }
          }
        }).show();
      } else {
        if (exp.getUrl() != null && !exp.getUrl().isEmpty())
          startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(exp.getUrl())));
      }
    }
  };

  public void launchExploit(final MsfExploit msfEx) {

    new Thread(new Runnable() {
      @Override
      public void run() {

        int length;
        String message;

        length = Toast.LENGTH_SHORT;

        try {
          if (msfEx.launch())
            message = msfEx.getName() + ":  Job started";
          else
            message = msfEx.getName() + ":  Launch failed";
        } catch (RPCClient.MSFException e) {
          message = String.format("launch failed: %s", e.getMessage());
          length = Toast.LENGTH_LONG;
        }

        final int flength = length;
        final String fmessage = message;

        ExploitFinder.this.runOnUiThread(new Runnable() {
          @Override
          public void run() {
            Toast.makeText(ExploitFinder.this, fmessage, flength).show();
          }
        });
      }
    }).start();
  }

  public ExploitFinder() {
    super
            (

                    R.string.exploit_finder,
                    R.string.exploit_finder_desc,

                    new Target.Type[]{Target.Type.ENDPOINT, Target.Type.REMOTE},
                    R.layout.plugin_exploit_finder,
                    R.drawable.action_exploit_finder
            );
  }

  private void setStartedState() {
    mSearchProgress.setVisibility(View.VISIBLE);

    final Target target = System.getCurrentTarget();

    job = Search.searchExploitForServices(target,
            new Search.Receiver<Exploit>() {
              private boolean somethingFound = false;

              @Override
              public void onItemFound(final Exploit exploit) {
                somethingFound = true;
                ExploitFinder.this.runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    target.addExploit(exploit);
                    mAdapter.notifyDataSetChanged();
                  }
                });
              }

              @Override
              public void onFoundItemChanged(Exploit exploit) {
                ExploitFinder.this.runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    mAdapter.notifyDataSetChanged();
                  }
                });
              }

              @Override
              public void onEnd() {
                ExploitFinder.this.runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    mSearchProgress.setVisibility(View.GONE);
                    mSearchFloatingActionButton.setImageDrawable(ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_stop_24dp));
                    buttonPlayed = true;
                    if (System.getCurrentExploits().size() == 0) {
                      new FinishDialog(getString(R.string.warning), getString(R.string.no_exploits_found), ExploitFinder.this).show();
                    } else if (!somethingFound) {
                      new ErrorDialog(getString(R.string.warning), getString(R.string.no_exploits_found), ExploitFinder.this).show();
                    }
                  }
                });
              }
            });
  }

  private void setStoppedState() {
    if (job != null)
      job.cancel(true);

    mSearchProgress.setVisibility(View.GONE);
    mSearchFloatingActionButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_play_arrow_24dp));
    buttonPlayed = false;
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.exploit_finder, menu);
    return super.onCreateOptionsMenu(menu);
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    SharedPreferences themePrefs = getSharedPreferences("THEME", 0);
    Boolean isDark = themePrefs.getBoolean("isDark", false);
    if (isDark)
      setTheme(R.style.DarkTheme);
    else
      setTheme(R.style.AppTheme);
    super.onCreate(savedInstanceState);

    Target t = System.getCurrentTarget();

    if (!t.hasOpenPorts())
      new FinishDialog(getString(R.string.warning), getString(R.string.no_open_ports), this).show();

    else if (!t.hasOpenPortsWithService())
      new FinishDialog(getString(R.string.warning), getString(R.string.no_infos_on_target), this).show();

    mSearchFloatingActionButton = (FloatingActionButton) findViewById(R.id.searchToggleButton);
    mSearchProgress = (ProgressBar) findViewById(R.id.searchActivity);
    mListView = (ListView) findViewById(android.R.id.list);
    mAdapter = new ExploitAdapter();

    UIThread = this;

    mListView.setAdapter(mAdapter);


    mListView.setOnItemClickListener(listener);

    mSearchFloatingActionButton.setOnClickListener(new OnClickListener() {
                                             @Override
                                             public void onClick(View v) {
                                               if (buttonPlayed) {
                                                 setStoppedState();
                                               } else {
                                                 setStartedState();
                                               }
                                             }
                                           }
    );
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.exploit_launch_all:
        mAdapter.getCount();
        for (int x = 0; x < mAdapter.getCount(); x++) {
          Exploit exp = mAdapter.getItem(x);
          if (exp instanceof MsfExploit) {
            launchExploit((MsfExploit) exp);
          }
        }
        return true;
      default:
        return super.onOptionsItemSelected(item);
    }
  }

  @Override
  public void onBackPressed() {
    setStoppedState();
    super.onBackPressed();
    overridePendingTransition(R.anim.fadeout, R.anim.fadein);
  }

  @Override
  public void onRpcChange(RPCClient currentValue) {
    // TODO: use Java Observer/Observable implementation
    if (UIThread == null)
      return;
    // rebuild view
    if (this != UIThread) {
      UIThread.onRpcChange(currentValue);
    } else {
      ExploitFinder.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
          mAdapter.notifyDataSetChanged();
        }
      });
    }
  }
}